/*****************************
 *@file: CustomList
 *@author: 陈吕唐
 *@desc：自定义滚动列表辅助(创建和隐藏Item节点,节点复用)(仅仅支持上下滑动)
 *@date: 2021-09-08	00:28
 *****************************/
import { CCInteger, Component, Layout, Node, ScrollView, UITransform, Widget, _decorator, instantiate, math, v3, warn } from 'cc';
import ICustomListItem from './ICustomListItem';
const { ccclass, property, menu } = _decorator;


@ccclass('CustomList')
//ccclass 装饰器的参数 name 指定了 cc 类的名称，cc 类名是 独一无二 的，这意味着即便在不同目录下的同名类也是不允许的。当需要获取相应的 cc 类时，可以通过其 cc 类名来查找
@menu('Libs/ScrollView/CustomList')
/**
 * 组件类装饰器 文档:https://docs.cocos.com/creator/3.3/manual/zh/scripting/decorator.html?h=%E8%A3%85%E9%A5%B0%E5%99%A8
 *此类装饰器是只能用来修饰 Component 的子类
 */
export class CustomList extends Component {
    /****************************************************************************************************************
     *Property 定义变量
     ****************************************************************************************************************/
    /**
     * 滚动列表展示的数据
     */
    protected _array: any[] = [];

    /**
     * 当前开始绘制索引
     */
    protected startLine: number = 0;


    /**
     * 滚动开始前Content的Y值
     */
    protected startPositionY: number = 0;

    /**
     * 模板Item的初始位置
     */
    private origin: math.Vec3 = v3(0, 0, 0);


    /**
     * Item行间隔
     */
    private lineSpace: number = 0;

    /**
     * Item行高(垂直滚动下为高度,水平滚动下为宽度)
     */
    private lineHeight: number = 0;


    /**
     * 列表行数目
     */
    private lineNumber: number = 0;

    /**
     * 一页可以放置行数目(计算得出)
     */
    private pageLineNumber: number = 0;

    /**
     * 可视化区域大小(垂直滚动下为高度,水平滚动下为宽度PX)
     */
    private viewSize: number = 0;

    /**
     * 列宽
     */
    private columnWidth: number = 0;


    /**
     * 列数目
     */
    private columnNumber: number = 1;


    /**
     * 一行或者一列有多个Item时候他们的间隔
     */
    private columnSpace: number = 0;

    /**
     * 是否已经开启性能优化模式
     */
    private _optimize: boolean = false;
    /***************************************************************
    * FIX
    ***************************************************************/
    /**
     * 计算状态中不要更新视图
     */
    private lock: boolean = true;
    /***************************************************************
    *PropertyUI 引用的控件
    ***************************************************************/
    @property({ type: ScrollView, tooltip: `绑定的滚动视图` })
    protected scroll: ScrollView = undefined!;

    @property({ type: Layout, tooltip: `列表Item容器节点` })
    protected itemLayout: Layout = undefined!;

    @property({ type: CCInteger, tooltip: `缓存列表行数,默认0零时候缓存一页` })
    protected cacheLine: number = 0;

    @property({ tooltip: `是否自动开启性能优化模式` })
    protected autoOptimize: boolean = true;

    @property({ tooltip: `列表Item组件名` })
    private itemName: string = ``;
    /****************************************************************************************************************
     * Lifecycle 生命周期相关方法
     ****************************************************************************************************************/
    protected onLoad() {
        this.initList();
    }

    protected onEnable() {
        this.updateView(this.array)
    }

    protected onDestroy() {

    }
    /****************************************************************************************************************
     * Public 公有方法
     ****************************************************************************************************************/
    /****************************************************************************************************************
     * private 私有方法
     ****************************************************************************************************************/

    /**
     * 监听滚动视图(滚动开始)
     * @param scroll 
     */
    private onScrollViewScrolling(scroll: ScrollView) {
        this.refreshItems(scroll)
    }

    /**
     * 监听滚动视图(滚动结束)
     * @param scroll 
     */
    private onScrollViewEnded(scroll: ScrollView) {
        this.refreshItems(scroll)
    }

    /**
     * ScrollView滚动后刷新Items
     */
    private refreshItems(scroll: ScrollView) {
        if (!this.optimize) {
            return;
        }

        if (!scroll.content) {
            warn(`未找到Scroll Content`)
            return;
        }

        let moveY: number = 0;
        if (scroll.vertical) {
            moveY = scroll.content.position.y - this.startPositionY
        } else {
            moveY = this.startPositionY - scroll.content.position.x;
        }

        /**
         * 距离初始状态移动了几行
         */
        let moveNumber = Math.floor(moveY / (this.lineHeight + this.lineSpace));

        /**
         * 当前应该从第几行开始绘制
         */
        let nowStart = moveNumber - 2;
        nowStart = nowStart >= 0 ? nowStart : 0;


        if (nowStart == this.startLine) {
            return;
        }

        /**
         * 要更新几行
         */
        let number = nowStart - this.startLine;

        /**
         * 开始更新索引
         */
        let start: number = 0;

        /**
         * 结束更新索引
         */
        let end: number = 0;
        if (number > 0) {
            start = this.startLine * this.columnNumber + this.itemLayout.node.children.length;
            end = start + number * this.columnNumber;
        } else {
            start = nowStart * this.columnNumber;
            end = this.startLine * this.columnNumber;
        }



        for (let i = start; i < end; i++) {

            if (i < 0) {
                continue;
            }

            if (i >= this.array.length) {
                continue;
            }

            this.updateItem(i);
        }

        this.startLine = nowStart;
    }



    /**
     * 初始化List
     */
    private initList() {
        if (!this.scroll) {
            this.scroll = this.node.getComponent(ScrollView)!;
        }

        if (!this.scroll) {
            warn(`未找到绑定的ScrollView,请设置`);
            return;
        }

        if (!this.scroll.content) {
            warn(`未找到绑定的Scroll Content,请设置`);
            return;
        }

        this.scroll.node.on(ScrollView.EventType.SCROLLING, this.onScrollViewScrolling, this);
        this.scroll.node.on(ScrollView.EventType.SCROLL_ENDED, this.onScrollViewEnded, this);

        if (!this.itemLayout) {
            this.itemLayout = this.scroll.content?.getComponent(Layout)!;
        }

        if (!this.itemLayout) {
            warn(`未找到绑定的contentLayout,请设置`);
            return;
        }

        /**
         * 模板节点
         */
        let item = this.scroll.content?.children[0];
        if (!item) {
            warn(`未找到模板节点`);
            return;
        }

        if (this.itemLayout.type == Layout.Type.GRID) {
            this.columnNumber = this.itemLayout.constraintNum;
        }
        let layoutWidget = this.itemLayout.node.getComponent(Widget);
        layoutWidget?.updateAlignment();

        this.lineSpace = this.scroll.vertical ? this.itemLayout.spacingY : this.itemLayout.spacingX;
        this.columnSpace = this.scroll.vertical ? this.itemLayout.spacingX : this.itemLayout.spacingY;

        this.origin = instantiate(item.position);
        let uITransform: UITransform = item.getComponent(UITransform)!;
        this.lineHeight = this.scroll.vertical ? uITransform.height : uITransform.width;
        this.columnWidth = this.scroll.vertical ? uITransform.width : uITransform.height;
        this.startPositionY = this.scroll.vertical ? this.scroll.content.position.y : this.scroll.content.position.x;


        let scrollUI = this.scroll.getComponent(UITransform);
        let widget = this.scroll.node.getComponent(Widget);
        widget?.updateAlignment();
        if (scrollUI) {
            this.viewSize = this.scroll.vertical ? scrollUI.height : scrollUI.width;
        }

        this.pageLineNumber = Math.ceil(this.viewSize / (this.lineHeight + this.lineSpace));

        this.lock = false;
        this.updateView(this.array);
    }



    /**
     * 设置Item容器大小
     * @param lineNumber 行数目
     */
    private setLayoutSize(lineNumber: number) {
        let ui = this.itemLayout.node.getComponent(UITransform);
        if (!ui) {
            return;
        }

        this.itemLayout.enabled = false;

        let before: math.Size = ui.contentSize;

        let size = lineNumber * (this.lineHeight + this.lineSpace) - this.lineSpace;

        if (this.scroll.vertical) {
            before.height = size;
        } else {
            before.width = size;
        }
    }


    /**
     * 设置Item节点位置
     * @param node 节点
     * @param index 索引
     */
    private setItemPosition(node: Node, index: number) {
        /**
         * 第几行
         */
        let line = Math.floor(index / this.columnNumber);
        /**
         * 第几列
         */
        let column = index % this.columnNumber;

        let p: math.Vec3 = node.position;

        p.x = this.origin.x + column * (this.columnWidth + this.columnSpace);
        p.y = this.origin.y - line * (this.lineHeight + this.lineSpace);
    }


    /**
     * 渲染节点
     * @param node 
     * @param index 
     */
    protected applyItem(node: Node, index: number) {
        let value = this._array[index];
        node.active = true;
        let item: ICustomListItem = this.getCustomListItem(node);
        item.value = value;
    }

    /**
     * 更新Item
     * @param index Item索引
     */
    private updateItem(index: number) {
        if (this.optimize) {
            let i = index % this.itemLayout.node.children.length;
            let node = this.itemLayout.node.children[i];
            this.setItemPosition(node, index);
            this.applyItem(node, index);
        } else {
            /**
             * 更新列表Item视图
             */
            let node = this.itemLayout.node.children[index];
            this.applyItem(node, index);
        }
    }

    /**
     * 数组更改时候还原设置
     */
    protected restoreSet() {
        /**
         * 性能优化还原初始设置
         */
        this._optimize = false;

        /**
         * 滚动还原到初始位置
         */
        let p: math.Vec3 = this.itemLayout.node.position;
        if (this.scroll.vertical) {
            p.y = this.startPositionY;
        } else {
            p.x = this.startPositionY;
        }

        this.startLine = 0;
    }

    /**
     * 根据列表数据初始化视图
     * @param array 列表数据
     */
    protected updateView(array: any[]) {

        if (!this.enabledInHierarchy) {
            return;
        }

        if (this.lock) {
            return;
        }

        if (!array) {
            return;
        }

        if (!this.scroll) {
            return;
        }

        this.restoreSet();

        /**
         * 多余节点移除或者隐藏
         */
        let nodes = this.itemLayout.node.children;
        for (let i = 0; i < nodes.length; i++) {
            let node = nodes[i];
            node.active = false;
            if (i < array.length) {
                continue;
            }

            node.active = false;

        }

        if (this.cacheLine <= 0) {
            this.cacheLine = this.pageLineNumber;
        }

        let limitLine = this.pageLineNumber + this.cacheLine;
        let limitNumber = limitLine * this.columnNumber;


        if (this.autoOptimize) {
            this._optimize = array.length > limitNumber;
        }

        if (this.optimize) {
            this.lineNumber = Math.ceil(array.length / this.columnNumber);
            this.itemLayout.enabled = false;
            this.setLayoutSize(this.lineNumber);
            let addNumber: number = limitNumber - nodes.length;
            for (let i = 0; i < addNumber; i++) {
                let node = this.createItem();
                this.itemLayout.node?.addChild(node);
            }
            let startIndex = this.startLine * this.columnNumber;
            for (let i = startIndex; i < startIndex + this.itemLayout.node.children.length; i++) {
                this.updateItem(i);
            }

        } else {
            /**
             * 没有优化情况下节点不足时候创建节点
             */
            this.itemLayout.enabled = true;
            let addNumber: number = array.length - this.itemLayout.node.children.length;
            for (let i = 0; i < addNumber; i++) {
                let node = this.createItem();
                this.itemLayout.node?.addChild(node);
            }

            for (let i = 0; i < array.length; i++) {
                this.updateItem(i);
            }
            this.itemLayout.updateLayout(true);
        }
    }

    /**
     * 创建一个列表的子节点
     */
    protected createItem(): Node {
        let content = this.itemLayout.node;
        /**
         * 作为模板拷贝的节点
         */
        let copyNode = content?.children[0];

        let node = instantiate(copyNode);

        return node!;
    }
    /*******************************************************************************************************************
     * Child(子类实现的方法) 
     ******************************************************************************************************************/
    /**
     * 获取列表Item组件
     * 需要在子类中重写
     * @param node Item节点
     */
    protected getCustomListItem(node: Node): ICustomListItem {
        let item = undefined!;
        if (this.itemName) {
            item = node.getComponent(this.itemName);
        }
        return item;
    }
    /****************************************************************************************************************
     * Property Get && Set 属性的 get && set 函数
     ****************************************************************************************************************/
    public get array(): any[] {
        return this._array;
    }
    public set array(value: any[]) {
        this._array = value;
        this.updateView(value)
    }

    /**
     * 是否已经开启性能优化模式
     */
    protected get optimize(): boolean {
        return this._optimize;
    }
}


